﻿/*
 * @Autor: PK
 * @Date: 2022/01/10 18:01:SS
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Itinero;
using Itinero.Attributes;
using Itinero.Navigation.Directions;
using Itinero.Navigation.Instructions;
using Itinero.Navigation.Language;
using Itinero.Profiles;

namespace ShkSimulation.desktop.component.tools.map {

	public class TrainProfile : Profile {

		private static Dictionary<String, int> SpeedProfiles =
			new Dictionary<string, int> {
				{"railway", 90},
				{"railway_link", 90},
				{"trunk", 70},
				{"trunk_link", 70},
				{"primary", 50},
				{"primary_link", 50},
				{"secondary", 45},
				{"secondary_link", 45},
				{"tertiary", 45},
				{"tertiary_link", 45},
				{"unclassified", 40},
				{"residential", 40},
				{"service", 20},
				{"services", 20},
				{"road", 20},
				{"yard", 20},
				{"siding", 5},
				{"ferry", 5},
				{"movable", 5},
				{"shuttle_train", 10},
				{"default", 10}
			};

		private static Dictionary<String, bool> AccessValues =
			new Dictionary<string, bool> {
				{"private", false},
				{"yes", true},
				{"no", false},
				{"permissive", true},
				{"destination", true},
				{"customers", false},
				{"designated", true},
				{"public", true},
				{"delivery", true},
				{"use_sidepath", false}
			};

		private static string[] Vehicles = new[] {"vehicle", "motor_vehicle", "motorcar", "train"};

		public TrainProfile() : base("train", ProfileMetric.TimeInSeconds,
			Vehicles, null, new TrainVehicle()) {
			_instructionGenerator = new FastInstructionGenerator(this);
		}

		//interprets access tags
		private static bool? CanAccess(IAttributeCollection attributes) {

			bool? last_access = null;
			string accessValue = null;
			if (attributes.TryGetValue("access", out accessValue) &&
			    AccessValues.ContainsKey(accessValue)) {
				var access = AccessValues[accessValue];
				last_access = access;
			}
			foreach (var vtype in Vehicles) {
				string accessKey = null;
				if (attributes.TryGetValue(vtype, out accessKey) && AccessValues.ContainsKey(accessKey)) {
					var access = AccessValues[accessKey];
					last_access = access;
				}
			}
			return last_access;
		}

		private static short? IsOneway(IAttributeCollection attributes, Whitelist whitelist, String name) {
			String oneway = null;
			if (attributes.TryGetValue(name, out oneway)) {
				whitelist.Add(name);
				if (!String.IsNullOrEmpty(oneway)) {
					if (oneway == "yes" ||
					    oneway == "true" ||
					    oneway == "1")
						return 1;
					if (oneway == "-1")
						return 2;
				}
			}
			return null;
		}


		internal static FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes,
			Whitelist whitelist) {
			if (attributes == null || attributes.Count == 0) {
				return Itinero.Profiles.FactorAndSpeed.NoFactor;
			}
			string highway = null;
			if (attributes.TryGetValue("highway", out highway))
				whitelist.Add("highway");
			var result = new FactorAndSpeed();
			var speed = 0.0f;
			short direction = 0;
			var canstop = true;
			//set highway to ferry when ferry.
			string route = null;
			if (attributes.TryGetValue("route", out route))
				whitelist.Add("route");
			if (route == "ferry") {
				highway = "ferry";
			}

			if (String.IsNullOrEmpty(highway))
				return Itinero.Profiles.FactorAndSpeed.NoFactor;
			//get default speed profiles
			var highway_speed = SpeedProfiles.ContainsKey(highway) ? (int?) SpeedProfiles[highway] : null;

			if (highway_speed != null) {
				speed = highway_speed.Value;
				direction = 0;
				canstop = true;
				if (highway == "railway" ||
				    highway == "railway_link")
					canstop = false;
			} else
				return Itinero.Profiles.FactorAndSpeed.NoFactor;

			if (CanAccess(attributes) == false)
				return Itinero.Profiles.FactorAndSpeed.NoFactor;

			//get maxspeed if any.
			string maxSpeed = null;
			if (attributes.TryGetValue("maxspeed", out maxSpeed)) {
				whitelist.Add("maxspeed");
				float lspeed;
				if (float.TryParse(maxSpeed, out lspeed))
					speed = lspeed * 0.75f;
			}

			//get maxweight and maxwidth constraints if any
			var maxweight = 0.0f;
			var maxwidth = 0.0f;
			string maxWeightString = null;
			if (attributes.TryGetValue("maxweight", out maxWeightString)) {
				whitelist.Add("maxweight");
				float.TryParse(maxWeightString, out maxweight);
			}

			string maxWidthString = null;
			if (attributes.TryGetValue("maxwidth", out maxWidthString)) {
				whitelist.Add("maxwidth");
				float.TryParse(maxWidthString, out maxwidth);
			}

			if (maxwidth != 0 || maxweight != 0) {
				result.Constraints = new[]
					{maxweight, maxwidth};
			}

			//get directional information
			String junction = null;
			if (attributes.TryGetValue("junction", out junction)) {
				whitelist.Add("junction");
				if (junction == "roundabout")
					direction = 1;
			}
			var ldirection = IsOneway(attributes, whitelist, "oneway");

			if (ldirection != null)
				direction = ldirection.Value;
			if (speed == 0)
				return Itinero.Profiles.FactorAndSpeed.NoFactor;
			result.SpeedFactor = 1.0f / (speed / 3.6f); // 1/m/s
			result.Value = result.SpeedFactor;
			result.Direction = direction;
			if (!canstop) {
				result.Direction += 3;
			}
			return result;
		}

		public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes) {
			return TrainProfile.FactorAndSpeed(attributes, new Whitelist());
		}

		/// </summary>
		/// <remarks>
		/// Default implementation compares attributes one-by-one.
		/// </remarks>
		public override sealed bool Equals(IAttributeCollection attributes1, IAttributeCollection attributes2) {
			return attributes1.ContainsSame(attributes2);
		}

		private IUnimodalInstructionGenerator _instructionGenerator;

		public override IUnimodalInstructionGenerator InstructionGenerator {
			get { return _instructionGenerator; }
		}

	}

	public class TrainVehicle : Itinero.Profiles.Vehicle {

		static FactorAndSpeed DefaultFactorAndSpeed = new FactorAndSpeed() {
			SpeedFactor = 1 / (50f / 3.6f),
			Value = 1 / (50f / 3.6f),
			Direction = 0
		};

		readonly string[] _vehicleTypes = new[] {"vehicle", "motor_vehicle", "motorcor", "train"};

		public TrainVehicle() {
			Register(new Profile("shortest", ProfileMetric.DistanceInMeters, _vehicleTypes, null, this));
			Register(new Profile(string.Empty, ProfileMetric.TimeInSeconds, _vehicleTypes, null, this));

			ProfileWhiteList.Add("railway");
			ProfileWhiteList.Add("maxspeed");
			ProfileWhiteList.Add("route");
			ProfileWhiteList.Add("barrier");
			ProfileWhiteList.Add("service");

			MetaWhiteList.Add("name");
			MetaWhiteList.Add("ref");
			MetaWhiteList.Add("bridge");
			MetaWhiteList.Add("tunnel");
			MetaWhiteList.Add("embankment");
			MetaWhiteList.Add("cutting");
			MetaWhiteList.Add("electrified");
			MetaWhiteList.Add("owner");
			MetaWhiteList.Add("operator");
			MetaWhiteList.Add("usage");
			MetaWhiteList.Add("from");
			MetaWhiteList.Add("to");
			MetaWhiteList.Add("via");
			MetaWhiteList.Add("voltage");
			MetaWhiteList.Add("wikipedia");
			MetaWhiteList.Add("embedded_rails");
			MetaWhiteList.Add("frequency");
			MetaWhiteList.Add("railway:track_ref");
			MetaWhiteList.Add("tracks");
			MetaWhiteList.Add("spur");
			MetaWhiteList.Add("spiding");
			MetaWhiteList.Add("crossover");
		}

		/// <summary>
		/// Gets the name of this vehicle.
		/// </summary>
		public override string Name => nameof(TrainVehicle);

		/// <summary>
		/// Gets the vehicle types.
		/// </summary>
		public override string[] VehicleTypes {
			get { return _vehicleTypes; }
		}

		public override bool AddToWhiteList(IAttributeCollection attributes, Whitelist whitelist) {
			return TrainProfile.FactorAndSpeed(attributes, whitelist).SpeedFactor > 0;
		}


		public override FactorAndSpeed FactorAndSpeed(IAttributeCollection attributes, Whitelist whitelist) {
			return DefaultFactorAndSpeed;
		}

	}

	internal class FastInstructionGenerator :
		IUnimodalInstructionGenerator {

		private Profile _profile;

		internal FastInstructionGenerator(Profile profile) {
			_profile = profile;
		}

		public IList<Itinero.Navigation.Instructions.Instruction> Generate(Route route, ILanguageReference language) {
			if (route.IsMultimodal()) {
				throw new ArgumentException("Cannot use a unimodal instruction generator on multimodal route.");
			}
			if (_profile.FullName.ToLowerInvariant() != route.Profile) {
				throw new ArgumentException(string.Format(
					"Cannot generate instructions with a generator for profile {0} for a route with profile {1}.",
					_profile.FullName, route.Profile));
			}
			var instructions = new List<Itinero.Navigation.Instructions.Instruction>();
			foreach (var position in route) {
				if (position.IsFirst())
					instructions.Add(GetStart(position, language));
				else if (position.IsLast())
					instructions.Add(GetStop(position, language));
				else if (position.GetMetaAttribute("junction") ==
				         "roundabout") {
					var instruction = GetRoundabout(position, language);
					if (instruction != null)
						instructions.Add(instruction);
				} else {
					var instruction = GetTurn(position, language);
					if (instruction != null)
						instructions.Add(instruction);
				}
			}
			return instructions;
		}

		private Itinero.Navigation.Instructions.Instruction GetStart(RoutePosition position,
			ILanguageReference language) {
			var instruction = new Itinero.Navigation.Instructions.Instruction {
				Shape = position.Shape,
				Type = "start"
			};
			var direction = position.Direction();
			instruction.Text = String.Format(language
				["Start {0}."], language[direction.ToString()
				.ToLower()]);
			return instruction;
		}

		// gets the last instruction
		private Itinero.Navigation.Instructions.Instruction
			GetStop(RoutePosition position, ILanguageReference language) {
			return new Itinero.Navigation.Instructions.Instruction {
				Text = language["Arrived at destination."],
				Shape = position.Shape,
				Type = "stop"
			};
		}

		private IEnumerable<Route.Branch> GetTraversable(IEnumerable<Route.Branch> branches) {
			foreach (var branch in branches) {
				var factorAndSpeed = _profile.FactorAndSpeed(branch.Attributes);
				if (factorAndSpeed.SpeedFactor != 0) {
					if (factorAndSpeed.Direction == 0 ||
					    factorAndSpeed.Direction == 3) {
						yield return branch;
					} else {
						if (branch.AttributesDirection) {
							if (factorAndSpeed.Direction == 1 ||
							    factorAndSpeed.Direction == 4) {
								yield return branch;
							}
						} else {
							if (factorAndSpeed.Direction == 2 ||
							    factorAndSpeed.Direction == 5) {
								yield return branch;
							}
						}
					}
				}
			}
		}


		//gets a roundabout instruction
		private Itinero.Navigation.Instructions.Instruction GetRoundabout(RoutePosition position,
			ILanguageReference language) {
			position.Next();
			if (string.IsNullOrEmpty(position.GetMetaAttribute("junction"))) {
				var instruction = new Itinero.Navigation.Instructions.Instruction {
					Shape = position.Shape,
					Type = "roundabout"
				};

				var exit = 1;
				var count = 1;
				var previous = position.Previous();
				while (previous != null && previous.Value.GetMetaAttribute("junction") == "roundabout") {
					var branches = previous.Value.Branches();
					if (branches != null) {
						branches = GetTraversable(branches);
						if (branches.Any())
							exit++;
					}
					count++;
					previous = previous.Value.Previous();
				}

				instruction.Text = string.Format(language["Take the {0}th exit at the next roundabout."], exit);
				if (exit == 1)
					instruction.Text = string.Format(language["Take the first exit at the next roundabout."]);
				else if (exit == 2)
					instruction.Text = string.Format(language["Take the second exit at the next roundabout."]);
				else if (exit == 3)
					instruction.Text = string.Format(language["Take the third exit at the next roundabout."]);

				instruction.Shape = position.Shape;
				return instruction;
			}

			return null;
		}

		//gets a turn
		private Itinero.Navigation.Instructions.Instruction
			GetTurn(RoutePosition position, ILanguageReference language) {
			var instruction = new Itinero.Navigation.Instructions.Instruction {
				Shape = position.Shape,
				Type = "turn"
			};
			var relativeDirection = position.RelativeDirection().Direction;
			var turnRelevant = false;
			var branches = position.Branches();
			if (branches != null) {
				var traversedBranches =
					GetTraversable(branches).ToList();

				if (relativeDirection == RelativeDirectionEnum.StraightOn &&
				    traversedBranches.Count >= 2)
					turnRelevant = true; // straight on at cross road

				if (relativeDirection != RelativeDirectionEnum.StraightOn &&
				    traversedBranches.Count > 0)
					turnRelevant = true; // an actual normal turn
			}
			if (turnRelevant) {
				var next = position.Next();
				string name = null;
				if (next != null)
					name = next.Value.GetMetaAttribute("name");
				if (!String.IsNullOrEmpty(name)) {
					instruction.Text = string.Format(language["Go {0} on {1}."],
						language[relativeDirection.ToString()
							.ToLower()], name);
					instruction.Shape = position.Shape;
				} else {
					instruction.Text = String.Format(language["Go {0}."],
						language[relativeDirection.ToString().ToLower()]);
					instruction.Shape = position.Shape;

				}
				return instruction;
			}
			return null;
		}

		public IList<Itinero.Navigation.Instructions.Instruction> Generate(Route route,
			ILanguageReference languageReference, CancellationToken cancellationToken) {
			return Generate(route, languageReference);
		}

	}

}